home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 2003 August / MW 8 2003 CD1.iso / Inside Macworld / Product News / gimp-1.2.4.sit / gimp-1.2.4 / tools / gimp-remote.c next >
Encoding:
C/C++ Source or Header  |  2003-01-07  |  10.0 KB  |  360 lines

  1. /* The GIMP -- an image manipulation program
  2.  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
  3.  * 
  4.  * gimp-remote.c
  5.  * Copyright (C) 2000-2003  Sven Neumann <sven@gimp.org>
  6.  *                          Simon Budig <simon@gimp.org>
  7.  *
  8.  * Tells a running gimp to open files by creating a synthetic drop-event.
  9.  *
  10.  * compile with
  11.  *  gcc -o gimp-remote `gtk-config --cflags --libs` -lXmu gimp-remote.c
  12.  *
  13.  * This program is free software; you can redistribute it and/or modify
  14.  * it under the terms of the GNU General Public License as published by
  15.  * the Free Software Foundation; either version 2 of the License, or
  16.  * (at your option) any later version.
  17.  *
  18.  * This program is distributed in the hope that it will be useful,
  19.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21.  * GNU General Public License for more details.
  22.  *
  23.  * You should have received a copy of the GNU General Public License
  24.  * along with this program; if not, write to the Free Software
  25.  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  26.  */
  27.  
  28. /* Disclaimer:
  29.  *
  30.  * It is a really bad idea to use Drag'n'Drop for inter-client
  31.  * communication. Dont even think about doing this in your own newly
  32.  * created application. We do this *only*, because we are in a
  33.  * feature freeze for Gimp 1.2 and adding a completely new communication
  34.  * infrastructure for remote controlling Gimp is definitely a new
  35.  * feature...
  36.  * Think about sockets or Corba when you want to do something similiar.
  37.  * We definitely consider this for Gimp 2.0.
  38.  *                                                Simon
  39.  */
  40.  
  41. /* TODO:
  42.  *
  43.  * Should try to execv the gimp in the same path as the gimp-remote
  44.  * executable, then fall back to execvp ("gimp", argv).
  45.  */
  46.  
  47. #include "config.h"
  48.  
  49. #include <errno.h>
  50. #include <unistd.h>
  51. #include <string.h>
  52. #include <stdlib.h>
  53.  
  54. #include <X11/Xmu/WinUtil.h>       /*  for XmuClientWindow ()  */
  55.  
  56. #include <gdk/gdkx.h>
  57. #include <gtk/gtk.h>
  58.  
  59. #include "libgimp/gimpfeatures.h"  /*  for GIMP_VERSION        */
  60.  
  61.  
  62. static gboolean  start_new = FALSE;
  63.  
  64.  
  65. static GdkWindow *
  66. gimp_remote_find_window (void)
  67. {
  68.   GdkWindow *result = NULL;
  69.   Window     root, parent;
  70.   Window    *children;
  71.   Atom       class_atom;
  72.   Atom       string_atom;
  73.   guint      nchildren;
  74.   gint       i;
  75.  
  76.   if (XQueryTree (gdk_display, gdk_root_window,
  77.                   &root, &parent, &children, &nchildren) == 0)
  78.     return NULL;
  79.   
  80.   if (! (children && nchildren))
  81.     return NULL;
  82.   
  83.   class_atom  = XInternAtom (gdk_display, "WM_CLASS", TRUE);
  84.   string_atom = XInternAtom (gdk_display, "STRING",   TRUE);
  85.  
  86.   for (i = nchildren - 1; i >= 0; i--)
  87.     {
  88.       Window  window;
  89.       Atom    ret_type;
  90.       gint    ret_format;
  91.       gulong  bytes_after;
  92.       gulong  nitems;
  93.       guchar *data;
  94.  
  95.       /*  The XmuClientWindow() function finds a window at or below the
  96.        *  specified window, that has a WM_STATE property. If such a
  97.        *  window is found, it is returned; otherwise the argument window
  98.        *  is returned.
  99.        */
  100.  
  101.       window = XmuClientWindow (gdk_display, children[i]);
  102.  
  103.       /*  We are searching the Gimp toolbox: Its WM_CLASS Property
  104.        *  has the values "toolbox\0Gimp\0". This is pretty relieable,
  105.        *  it is more reliable when we ask a special property, explicitely
  106.        *  set from the gimp. See below... :-)
  107.        */
  108.  
  109.       if (XGetWindowProperty (gdk_display, window,
  110.                               class_atom,
  111.                               0, 32,
  112.                               FALSE,
  113.                               string_atom,
  114.                               &ret_type, &ret_format, &nitems, &bytes_after,
  115.                               &data) == Success &&
  116.           ret_type)
  117.         {
  118.           if (nitems > 12                        &&
  119.               strcmp (data,     "toolbox") == 0  &&
  120.               strcmp (data + 8, "Gimp")    == 0) 
  121.             {
  122.               XFree (data);
  123.               result = gdk_window_foreign_new (window);
  124.               break;
  125.             }
  126.  
  127.           XFree (data);
  128.         }
  129.     }
  130.   
  131.   XFree (children);
  132.  
  133.   return result;
  134. }
  135.  
  136. static void  
  137. source_selection_get (GtkWidget          *widget,
  138.               GtkSelectionData   *selection_data,
  139.               guint               info,
  140.               guint               time,
  141.               gpointer            data)
  142. {
  143.   gchar *uri = (gchar *) data;
  144.  
  145.   gtk_selection_data_set (selection_data,
  146.                           selection_data->target,
  147.                           8, uri, strlen (uri));
  148.   gtk_main_quit ();
  149. }
  150.  
  151. static gboolean
  152. toolbox_hidden (gpointer data)
  153. {
  154.   g_printerr ("Could not connect to the Gimp.\n"
  155.               "Make sure that the Toolbox is visible!\n");
  156.   gtk_main_quit ();
  157.  
  158.   return FALSE;
  159. }
  160.   
  161. static void
  162. usage (const gchar *name)
  163. {
  164.   g_print ("gimp-remote version %s\n\n", GIMP_VERSION);
  165.   g_print ("Tells a running Gimp to open a (local or remote) image file.\n\n"
  166.        "Usage: %s [options] [FILE|URI]...\n\n", name);
  167.   g_print ("Valid options are:\n" 
  168.        "  -h --help       Output this help.\n"
  169.        "  -v --version    Output version info.\n"
  170.        "  -n --new        Start gimp if no active gimp window was found.\n\n");
  171.   g_print ("Example:  %s http://www.gimp.org/icons/frontpage-small.gif\n"
  172.        "     or:  %s localfile.png\n\n", name, name);
  173. }
  174.  
  175. static void
  176. start_new_gimp (GString *file_list)
  177. {     
  178.   gint    i;
  179.   gchar **argv;
  180.   
  181.   file_list = g_string_prepend (file_list, "gimp\n");
  182.   argv = g_strsplit (file_list->str, "\n", 0);
  183.   g_string_free (file_list, TRUE);
  184.  
  185.   for (i = 1; argv[i]; i++)
  186.     {
  187.       if (g_strncasecmp ("file:", argv[i], 5) == 0)
  188.     argv[i] += 5;
  189.     }
  190.   execvp ("gimp-1.2", argv);
  191.       
  192.   /*  if execvp returns, there was an arror  */
  193.   g_printerr ("Couldn't start gimp-1.2 for the following reason: %s\n",
  194.               g_strerror (errno));
  195.   exit (-1);
  196. }
  197.  
  198. static void
  199. parse_option (const gchar *progname,
  200.               const gchar *arg)
  201. {
  202.   if (strcmp (arg, "-v") == 0 || 
  203.       strcmp (arg, "--version") == 0)
  204.     {
  205.       g_print ("gimp-remote version %s\n", GIMP_VERSION);
  206.       exit (0);
  207.     }
  208.   else if (strcmp (arg, "-h") == 0 || 
  209.        strcmp (arg, "-?") == 0 || 
  210.        strcmp (arg, "--help") == 0 || 
  211.        strcmp (arg, "--usage") == 0)
  212.     {
  213.       usage (progname);
  214.       exit (0);
  215.     }
  216.   else if (strcmp (arg, "-n") == 0 ||
  217.        strcmp (arg, "--new") == 0)
  218.     {
  219.       start_new = TRUE;
  220.     }
  221.   else
  222.     {
  223.       g_print ("Unknown option %s\n", arg);
  224.       g_print ("Try gimp-remote --help to get detailed usage instructions.\n");
  225.       exit (0);
  226.     }
  227. }
  228.  
  229. gint 
  230. main (gint    argc, 
  231.       gchar **argv)
  232. {
  233.   GtkWidget *source;
  234.   GdkWindow *gimp_window;
  235.     
  236.   GdkDragContext  *context;
  237.   GdkDragProtocol  protocol;
  238.   
  239.   GdkAtom   sel_type;
  240.   GdkAtom   sel_id;
  241.   GList    *targetlist;
  242.   guint     timeout;
  243.   gboolean  options = TRUE;
  244.   
  245.   GString  *file_list = g_string_new (NULL);
  246.   gchar    *cwd       = g_get_current_dir ();
  247.   gchar    *file_uri  = "";
  248.   guint    i;
  249.   
  250.   for (i = 1; i < argc; i++)
  251.     {
  252.       if (strlen (argv[i]) == 0)
  253.         continue;
  254.       
  255.       if (options && *argv[i] == '-')
  256.         {
  257.           if (strcmp (argv[i], "--"))
  258.             {
  259.               parse_option (argv[0], argv[i]);
  260.               continue;
  261.             }
  262.           else
  263.             {
  264.               /*  everything following a -- is interpreted as arguments  */
  265.               options = FALSE;
  266.               continue;
  267.             }
  268.         }
  269.       
  270.       /* If not already a valid URI */
  271.       if (g_strncasecmp ("file:", argv[i], 5) &&
  272.           g_strncasecmp ("ftp:",  argv[i], 4) &&
  273.           g_strncasecmp ("http:", argv[i], 5))
  274.         {
  275.           if (g_path_is_absolute (argv[i]))
  276.             file_uri = g_strconcat ("file:", argv[i], NULL);
  277.           else
  278.             file_uri = g_strconcat ("file:", cwd, "/", argv[i], NULL);
  279.         }
  280.       else
  281.         file_uri = g_strdup (argv[i]);
  282.       
  283.       if (file_list->len > 0)
  284.         file_list = g_string_append_c (file_list, '\n');
  285.       
  286.       file_list = g_string_append (file_list, file_uri);
  287.       g_free (file_uri);
  288.     }
  289.  
  290.   if (file_list->len == 0) 
  291.     {
  292.       usage (argv[0]);
  293.       return EXIT_SUCCESS;
  294.     }
  295.  
  296.   gtk_init (&argc, &argv); 
  297.   
  298.   /*  locate Gimp window */
  299.   gimp_window = gimp_remote_find_window ();
  300.  
  301.   if (!gimp_window)
  302.     {
  303.       if (start_new)
  304.         start_new_gimp (file_list);
  305.       
  306.       g_printerr ("No gimp window found on display %s\n", gdk_get_display ());
  307.       return EXIT_FAILURE;
  308.     }
  309.  
  310.   gdk_drag_get_protocol (GDK_WINDOW_XWINDOW (gimp_window), &protocol);
  311.   if (protocol != GDK_DRAG_PROTO_XDND)
  312.     {
  313.       g_printerr ("Gimp Window doesnt use Xdnd-Protocol - huh?\n");
  314.       return EXIT_FAILURE;
  315.     }    
  316.  
  317.   /*  Problem: If the Toolbox is hidden via Tab (gtk_widget_hide)
  318.    *  it does not accept DnD-Operations and gtk_main() will not be
  319.    *  terminated. If the Toolbox is simply unmapped (by the Windowmanager)
  320.    *  DnD works. But in both cases gdk_window_is_visible () == 0.... :-( 
  321.    *  To work around this add a timeout and abort after 1.5 seconds.
  322.    */
  323.  
  324.   timeout = gtk_timeout_add (1500, toolbox_hidden, NULL);
  325.  
  326.   /*  set up an DND-source  */
  327.   source = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  328.   gtk_signal_connect (GTK_OBJECT (source), "selection_get",
  329.                       GTK_SIGNAL_FUNC (source_selection_get), file_list->str);
  330.   gtk_widget_realize (source);
  331.  
  332.  
  333.   /*  specify the id and the content-type of the selection used to
  334.    *  pass the URIs to Gimp.
  335.    */
  336.   sel_id   = gdk_atom_intern ("XdndSelection", FALSE);
  337.   sel_type = gdk_atom_intern ("text/uri-list", FALSE);
  338.   targetlist = g_list_prepend (NULL, GUINT_TO_POINTER (sel_type));
  339.  
  340.   /*  assign the selection to our DnD-source  */
  341.   gtk_selection_owner_set (source, sel_id, GDK_CURRENT_TIME);
  342.   gtk_selection_add_target (source, sel_id, sel_type, 0);
  343.  
  344.   /*  drag_begin/motion/drop  */
  345.   context = gdk_drag_begin (source->window, targetlist);
  346.  
  347.   gdk_drag_motion (context, gimp_window, protocol, 0, 0,
  348.                    GDK_ACTION_COPY, GDK_ACTION_COPY, GDK_CURRENT_TIME);
  349.  
  350.   gdk_drag_drop (context, GDK_CURRENT_TIME);
  351.  
  352.   /*  finally enter the mainloop to handle the events  */
  353.   gtk_main ();
  354.  
  355.   gtk_timeout_remove (timeout);
  356.   g_string_free (file_list, TRUE);
  357.   
  358.   return EXIT_SUCCESS;
  359. }
  360.